package com.coscon.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.coscon.mapper.FileDeleteLogMapper;
import com.coscon.mapper.FilePathLogMapper;
import com.coscon.mapper.FilePlayLogMapper;
import com.coscon.mapper.SysConfigMapper;
import com.coscon.model.IrcaFile;
import com.coscon.model.db.FileDeleteLog;
import com.coscon.model.db.FilePathLog;
import com.coscon.model.db.FilePlayLog;
import com.coscon.model.db.SysConfig;
import com.coscon.service.ConfigService;
import com.coscon.service.FileService;
import com.coscon.utils.AESUtil;
import com.coscon.utils.DateUtils;
import com.coscon.utils.IpAddressUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.util.*;
import java.util.List;

/**
 * 文件浏览相关
 */
@Service
@Slf4j
public class FileServiceImpl implements FileService
{
    @Resource
    private SysConfigMapper sysConfigMapper;

    //已经有图标的格式集合
    private String[] extNameIcoArray = new String[]{
            "apk","exe","pdf","php","png","doc","docx","zip","rar","gif","avi"
            ,"mov","mp3","wav","mp4","psd","js","css"
            ,"html","wma","bmp","jpg","cab","msg","mid","tif","m3u","xlsx","wmv","txt"
            ,"ini","jpeg","url","bat","jar","java","sql","xml","py","asp","rupy"
            ,"jmx","json","mkv","torrent","cue","yml","7z","ape","pptx","xls","sh"
            ,"rmvb","dmg","flac","chm","pkg","ico","log"
    };

    /**
     * 公用的加密，key与iv
     */
    private static final String COMMON_AES_KEY = "reca01_data_keys";//16字节,以后从文件中读取
    private static final String COMMON_AES_IV = "reca01_data_keys";//16字节以后从文件中读取

    @Resource
    private FilePathLogMapper filePathLogMapper;
    @Resource
    private FilePlayLogMapper filePlayLogMapper;
    @Resource
    private FileDeleteLogMapper fileDeleteLogMapper;

    @Resource
    private ConfigService configService;


    @Override
    @Transactional
    public List<IrcaFile> getFileList(String path) {

        if(StringUtils.isBlank(path)){//如果参数路径为空，则返回跟路径
            return this.listRoots();
        }
        //获取配置
        Map<String, String> mapConfig = this.configService.getSysconfigValByKeys(
                "file_view_hidden_file"
                ,"file_view_file_sort"
                ,"file_view_hidden_folder"
                ,"file_view_list_min_mb"
                ,"file_view_ext_type"
                ,"file_view_ext_video"
                ,"file_view_ext_audio"
                ,"file_view_ext_office"
        );

        //从配置中获取，是否包含隐藏文件展示
        String fileViewHiddenFile =mapConfig.get("file_view_hidden_file")!=null?mapConfig.get("file_view_hidden_file").toString():null;
        //从配置中获取文件类别排序方式
        String fileViewFileSort = mapConfig.get("file_view_file_sort")!=null?mapConfig.get("file_view_file_sort").toString():null;
        final int fileViewFileSortInt = NumberUtils.toInt(fileViewFileSort,1);

        //是否排除空文件夹
        String fileViewHiddenFolder =mapConfig.get("file_view_hidden_folder")!=null?mapConfig.get("file_view_hidden_folder").toString():null;
        int fileViewHiddenFolderInt = NumberUtils.toInt(fileViewHiddenFolder,1);

        //排除小文件，小于配置的大小(MB)则不展示
        String fileViewListMinMb = mapConfig.get("file_view_list_min_mb")!=null?mapConfig.get("file_view_list_min_mb").toString():null;
        int fileViewListMinMbInt = NumberUtils.toInt(fileViewListMinMb,0) *1024*1024;//byte

        //文件浏览的格式配置
        String fileViewExtType =mapConfig.get("file_view_ext_type")!=null?mapConfig.get("file_view_ext_type").toString():null;
        int fileViewExtTypeInt = NumberUtils.toInt(fileViewExtType,0);
        //展示的格式集合
        List<String> listViewExtName = null;
        if(fileViewExtTypeInt!=0){
            listViewExtName = this.getListViewExtName(mapConfig);
            log.debug(StringUtils.join(listViewExtName,","));
        }
        

        //保存访问的path信息到db
        FilePathLog filePathLog = new FilePathLog();
        filePathLog.setPath(path);
        filePathLog.setAddDateTime(DateUtils.nowStr());
        filePathLog.setOsType(SystemUtils.OS_NAME);
        filePathLog.setHostName(IpAddressUtils.getLocalHostName());
        this.filePathLogMapper.insert(filePathLog);

        List<IrcaFile> list = new ArrayList<>();
        //第一个元素，添加当前目录的上一级目录
        IrcaFile parentPathFrist = new IrcaFile();
        File parentFile = new File(path);
        parentPathFrist.setFileIs(false);
        parentPathFrist.setIcoName("parentFolder.png");
        parentPathFrist.setFileName("[..上一级..]");
        parentPathFrist.setFilePath(parentFile.getParent());
        parentPathFrist.setFilePathCode(filePathEncrypt(parentFile.getParent()));
        list.add(parentPathFrist);

        //定义返回的list
        List<IrcaFile> listRelFile = new ArrayList<>();
        //list file
        File[] listFile =  FileUtil.ls(path);
        for(File f : listFile){
            //排除隐藏文件
            if(StringUtils.equalsIgnoreCase(fileViewHiddenFile,"1") && f.isHidden()) {
                continue;
            }
            //判断文件大小，是否符合展示
            if (fileViewListMinMbInt>0 && f.isFile()) {
                if(f.length()<fileViewListMinMbInt){//文件大小，小于 下限值，则不处理
                    continue;//进入for的下一个
                }
            }
            //判断展示文件格式
            if(listViewExtName!=null && !listViewExtName.isEmpty() && f.isFile() && StringUtils.isNotBlank(f.getName())){
                String fileExtName = StringUtils.lowerCase( FileUtil.extName(f.getName()));
                if(StringUtils.isNotBlank(fileExtName) && !listViewExtName.contains(fileExtName)){
                    continue;//进入for的下一个
                }
            }
            //创建IrcaFile对象
            IrcaFile ircaFile = new IrcaFile();
            if(f.isDirectory()){//如果是目录，则对路径进行url编码
                //判断文件夹是否为空
                try {
                    ircaFile.setFolderIsEmpty(FileUtils.isEmptyDirectory(f) );
                } catch (IOException e) {
                    ircaFile.setFolderIsEmpty(true);
                }
                ircaFile.setIcoName(ircaFile.isFolderIsEmpty()?"folder-empty.png":"folder.png");
            }
            //排除（不展示）空的文件夹
            if(fileViewHiddenFolderInt==2 && f.isDirectory() && ircaFile.isFolderIsEmpty()){
                continue;
            }
            //设置基础参数
            ircaFile.setFileIs(f.isFile() );
            ircaFile.setFilePath(f.getPath());
            ircaFile.setFilePathCode(this.filePathEncrypt(f.getPath()));

            if(f.isFile()){//如果是文件
                ircaFile.setIcoName(getFileShowIcoByName(f.getName()));
                ircaFile.setFileSize(FileUtils.byteCountToDisplaySize(f.length() ));
                ircaFile.setLastUpdateDateTime(DateFormatUtils.format(FileUtil.lastModifiedTime(f),DateUtils.DEFAULT_DATE_TIME_FORMAT));
            }
            //当不是目录，也不是文件时,图标赋值为未知
            if(StringUtils.isBlank(ircaFile.getIcoName())){
                ircaFile.setIcoName("unknown.png");
            }
            ircaFile.setFileName(f.getName());
            //添加到集合
            listRelFile.add(ircaFile);
        }

        //排序（依旧配置的排序规则）
        this.sortFileList(listRelFile,fileViewFileSortInt);

        //添加到最终返回的集合中
        list.addAll(listRelFile);

        //末尾，添加一个：上一级
        IrcaFile parentPathLast = new IrcaFile();
        BeanUtil.copyProperties(parentPathFrist,parentPathLast);
        list.add(parentPathLast);

        return list;
    }

    @Override
    public List<IrcaFile> listRoots() {
        File[] listRoots = File.listRoots();
        List<IrcaFile> list = new ArrayList<>();
        for(File f : listRoots){
            IrcaFile ircaFile = new IrcaFile();
            ircaFile.setFileIs(f.isFile() );
            ircaFile.setFilePath(f.getPath());
            ircaFile.setFilePathCode(this.filePathEncrypt(f.getPath()));
            if(f.isDirectory()){//如果是目录，则对路径进行url编码
                //判断文件夹是否为空
                try {
                    ircaFile.setFolderIsEmpty(FileUtils.isEmptyDirectory(f) );
                } catch (IOException e) {
                    ircaFile.setFolderIsEmpty(true);
                }
                ircaFile.setIcoName(ircaFile.isFolderIsEmpty()?"folder-empty.png":"folder.png");
            }
            if(f.isFile()){
                //先设置给默认
                ircaFile.setIcoName(getFileShowIcoByName(f.getName()));
                ircaFile.setFileSize(FileUtils.byteCountToDisplaySize(f.length() ));
                //根据后缀名设置对应的图片
            }
            //当不是目录，也不是文件时,图标赋值为未知
            if(StringUtils.isBlank(ircaFile.getIcoName())){
                ircaFile.setIcoName("unknown.png");
            }
            ircaFile.setFileName(f.getName());
            //如果filename为空，则直接赋值路径
            if(StringUtils.isBlank(ircaFile.getFileName())){
                ircaFile.setFileName("ROOT");
                ircaFile.setIcoName("drive-hd.png");
            }
            list.add(ircaFile);
        }
        return  list;
    }

    @Override
    public void sortFileList(List<IrcaFile> listRelFile, int fileViewFileSortInt) {
        //对真实的文件集合进行根据名称排序
        Collections.sort(listRelFile, new Comparator<IrcaFile>() {
            @Override
            public int compare(IrcaFile o1, IrcaFile o2) {
                if(fileViewFileSortInt==1){//文件名排序（顺序）
                    String name1 = o1.getFileName();
                    String name2 = o2.getFileName();
                    if(StringUtils.isNotEmpty(name1) && StringUtils.isNotEmpty(name2)){
                        return name1.compareTo(name2);
                    }
                }else if(fileViewFileSortInt==2){//文件创建时间排序（顺序）,因为暂时创建时间为空，则采用更新时间
                    if(StringUtils.isNotEmpty(o1.getLastUpdateDateTime()) && StringUtils.isNotEmpty(o2.getLastUpdateDateTime())){
                        return o1.getLastUpdateDateTime().compareTo(o2.getLastUpdateDateTime());
                    }
                }else if(fileViewFileSortInt==3){//文件更新时间排序（顺序）
                    if(StringUtils.isNotEmpty(o1.getLastUpdateDateTime()) && StringUtils.isNotEmpty(o2.getLastUpdateDateTime())){
                        return o1.getLastUpdateDateTime().compareTo(o2.getLastUpdateDateTime());
                    }
                }else if(fileViewFileSortInt==4){//文件名排序（倒序）
                    String name1 = o1.getFileName();
                    String name2 = o2.getFileName();
                    if(StringUtils.isNotEmpty(name1) && StringUtils.isNotEmpty(name2)){
                        return name2.compareTo(name1);
                    }
                }else if(fileViewFileSortInt==5){//文件创建时间排序（倒序）
                    if(StringUtils.isNotEmpty(o1.getLastUpdateDateTime()) && StringUtils.isNotEmpty(o2.getLastUpdateDateTime())){
                        return o2.getLastUpdateDateTime().compareTo(o1.getLastUpdateDateTime());
                    }
                }else if(fileViewFileSortInt==6){//文件更新时间排序（倒序）
                    if(StringUtils.isNotEmpty(o1.getLastUpdateDateTime()) && StringUtils.isNotEmpty(o2.getLastUpdateDateTime())){
                        return o2.getLastUpdateDateTime().compareTo(o1.getLastUpdateDateTime());
                    }
                }
                return -1;
            }
        });
    }

    @Override
    public String getFileShowIcoByName(String fileName) {
        String iconame = "default.png";
        if(StringUtils.isNotBlank(fileName)){
            String execName = StringUtils.lowerCase(FileUtil.extName(fileName)) ;
            //判断扩展名是否包含在已有的图标数组中
            if(ArrayUtils.contains(extNameIcoArray,StringUtils.lowerCase(execName))){
                iconame = execName + ".png";
            }
        }
        return StringUtils.lowerCase(iconame) ;
    }

    @Override
    public String getUserHomePath() {
        return SystemUtils.USER_HOME;
    }



    /**
     * @param @param ming
     * @param @return 设定文件
     * @return String    返回类型
     * @Title: common_DataEncrypt
     * @Description: (通用的数据加密方法)
     */
    @Override
    public  String filePathEncrypt(String ming) {
        return baseEncrypt(ming, COMMON_AES_KEY, COMMON_AES_IV);
    }

    /**
     * @param @param mi
     * @param @return 设定文件
     * @return String    返回类型
     * @Title: common_DataDecrypt
     * @Description: (通用的数据解密)
     */
    @Override
    public  String filePathDecrypt(String mi) {
        return baseDecrypt(mi, COMMON_AES_KEY, COMMON_AES_IV);
    }

    @Override
    public boolean openFile(String filePath) {
        boolean f = false;
        if(StringUtils.isBlank(filePath)){
            return f;
        }
        //保存访问的path信息到db
        FilePlayLog filePlayLog = new FilePlayLog();
        filePlayLog.setPath(filePath);
        filePlayLog.setAddDateTime(DateUtils.nowStr());
        filePlayLog.setOsType(SystemUtils.OS_NAME);
        filePlayLog.setHostName(IpAddressUtils.getLocalHostName());


        if(Desktop.isDesktopSupported() && StringUtils.isNotBlank(filePath)){
            try {
                Desktop.getDesktop().open(new File(filePath));
                f = true;
                filePlayLog.setExecResult("OK");
            } catch (IOException e) {
               log.warn(e.getMessage());
                filePlayLog.setExecResult("ERROR:" + e.getMessage());
            }
        }else{
            log.warn("Desktop.isDesktopSupported() 不支持");
        }

        //保存日志
        SysConfig syslogPlayFileLogObj  = sysConfigMapper.getConfigByKey("syslog_play_file_log");
        String syslogPlayFileLog = syslogPlayFileLogObj!=null?syslogPlayFileLogObj.getConfigVal():null;
        if(StringUtils.equalsIgnoreCase(syslogPlayFileLog,"1")){
            this.filePlayLogMapper.insert(filePlayLog);
        }

        return f;
    }

    @Override
    public boolean deleteFile(String filePath) {
        boolean f = false;
        if(StringUtils.isBlank(filePath)){
            return f;
        }
        //从配置中获取删除文件的动作类型
        SysConfig fileDeleteActionObj  = sysConfigMapper.getConfigByKey("file_delete_action");
        String fileDeleteAction = fileDeleteActionObj!=null?fileDeleteActionObj.getConfigVal():null;
        int action  = NumberUtils.toInt(fileDeleteAction,1);//1:移到回收站，2：物理删除，无法还原,此值从config中获取
        if(action==1){
            //文件/或者文件夹 移到回收站
            try {
                com.sun.jna.platform.FileUtils.getInstance().moveToTrash(new File(filePath));
                f = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else{//物理删除
            f = FileUtil.del(filePath);
        }

        //记录日志
        SysConfig syslogDeleteFileLogObj  = sysConfigMapper.getConfigByKey("syslog_delete_file_log");
        String syslogDeleteFileLog = syslogDeleteFileLogObj!=null?syslogDeleteFileLogObj.getConfigVal():null;
        if(StringUtils.equalsIgnoreCase(syslogDeleteFileLog,"1")){
            FileDeleteLog deleteLog = new FileDeleteLog();
            deleteLog.setPath(filePath);
            deleteLog.setDeleteDateTime(DateUtils.nowStr());
            deleteLog.setOsType(SystemUtils.OS_NAME);
            deleteLog.setHostName(IpAddressUtils.getLocalHostName());
            deleteLog.setActionType(action==1?"移到回收站":"物理删除" );
            fileDeleteLogMapper.insert(deleteLog);
        }
        return f;

    }

    @Override
    public String getLastFilePathLog() {
        QueryWrapper<FilePathLog> qw = new QueryWrapper<FilePathLog>();
        qw.orderByDesc("ADD_DATE_TIME");
        IPage<FilePathLog> pageObj = new Page<>(1, 1);
        //执行查询
        pageObj = this.filePathLogMapper.selectPage(pageObj, qw);
        FilePathLog filePathLog = null;
        if(pageObj.getRecords()!=null && !pageObj.getRecords().isEmpty()){
            filePathLog = pageObj.getRecords().get(0);
        }
        String path = null;
        if(filePathLog!=null && StringUtils.isNotBlank(filePathLog.getPath())){
            File f = new File(filePathLog.getPath());
            if(f.exists() && f.isDirectory()){
                path = filePathLog.getPath();
            }
        }
        return path;
    }

    @Override
    public List<String> getListViewExtName(Map<String, String> mapConfig) {
        //展示的格式集合
        List<String> listViewExtName = new ArrayList<>();
        //文件浏览的格式配置
        String fileViewExtType =mapConfig.get("file_view_ext_type")!=null?mapConfig.get("file_view_ext_type").toString():null;
        int fileViewExtTypeInt = NumberUtils.toInt(fileViewExtType,0);
        if(0==fileViewExtTypeInt){//展示所有格式的文件
            return listViewExtName;
        }
        String file_view_ext_video = StringUtils.replace(mapConfig.get("file_view_ext_video")!=null?mapConfig.get("file_view_ext_video").toString():null,";",",");
        file_view_ext_video = StringUtils.lowerCase(file_view_ext_video);
        String file_view_ext_audio = StringUtils.replace(mapConfig.get("file_view_ext_audio")!=null?mapConfig.get("file_view_ext_audio").toString():null,";",",");
        file_view_ext_audio = StringUtils.lowerCase(file_view_ext_audio);
        String file_view_ext_office = StringUtils.replace(mapConfig.get("file_view_ext_office")!=null?mapConfig.get("file_view_ext_office").toString():null,";",",");
        file_view_ext_office = StringUtils.lowerCase(file_view_ext_office);
        if(fileViewExtTypeInt==2 || fileViewExtTypeInt==1){//展示仅视频文件
            if(StringUtils.isNotBlank(file_view_ext_video)){
                StringTokenizer st = new StringTokenizer(file_view_ext_video,",");
                while(st.hasMoreTokens()){
                    String extName = st.nextToken();
                    if(!listViewExtName.contains(extName)){
                        listViewExtName.add(extName);
                    }
                }
            }
        }
        if(fileViewExtTypeInt==3 || fileViewExtTypeInt==1){//展示仅音频文件
            if(StringUtils.isNotBlank(file_view_ext_audio)){
                StringTokenizer st = new StringTokenizer(file_view_ext_audio,",");
                while(st.hasMoreTokens()){
                    String extName = st.nextToken();
                    if(!listViewExtName.contains(extName)){
                        listViewExtName.add(extName);
                    }
                }
            }
        }
        if(fileViewExtTypeInt==4){//展示办公文件格式
            if(StringUtils.isNotBlank(file_view_ext_office)){
                StringTokenizer st = new StringTokenizer(file_view_ext_office,",");
                while(st.hasMoreTokens()){
                    String extName = st.nextToken();
                    if(!listViewExtName.contains(extName)){
                        listViewExtName.add(extName);
                    }
                }
            }
        }
        return listViewExtName;
    }


    private  String baseEncrypt(String passwordStr, String aesKey, String aesIv) {
        String mi = null;
        if (StringUtils.isEmpty(passwordStr)) {
            return null;
        }
        try {
            AESUtil aes = new AESUtil(aesIv);
            byte[] miByte = aes.encrypt(passwordStr, aesKey);
            mi = aes.parseByte2HexStr(miByte);
        } catch (IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException | UnsupportedEncodingException e) {
            e.printStackTrace();
        } // TODO Auto-generated catch block

        return mi;
    }

    private  String baseDecrypt(String mi, String aesKey, String aesIv) {
        String ming = null;
        if (StringUtils.isEmpty(mi)) {
            return null;
        }
        AESUtil aes = new AESUtil(aesIv);
        ming = aes.decrypt(aes.hexStringToBytes(mi), aesKey);
        return ming;
    }
}
